Enhance support for switch statements - #99
Conversation
Coverage reportCaution Test run failed
Show new covered files 🐣
Show files with reduced coverage 🔻
Test suite run failedFailed tests: 2/1159. Failed suites: 1/65.Report generated by 🧪jest coverage report action from 52781ca |
|
@coderabbitai review |
✅ Action performedReview finished.
|
WalkthroughChangesEnum switch support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CompilationUnit
participant prechecks as prechecks.ts
participant Checker
participant CodeGenerator
CompilationUnit->>prechecks: discover and register enum declarations
prechecks->>Checker: validate enum members and methods
Checker->>Checker: validate enum switch selectors and labels
Checker->>CodeGenerator: compile validated enum switch
CodeGenerator->>CodeGenerator: call Enum.ordinal()
CodeGenerator->>CodeGenerator: generate integer switch dispatch
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/compiler/code-generator.ts (1)
1407-1424: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy liftResolve enum case labels before generating integer switch keys
case REDis anExpressionName, not aLiteral. Line 1416 therefore throws aTypeErrorbefore generating switch bytecode. Resolve each enum case constant to its ordinal, matching the selector'sEnum.ordinal()conversion.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/compiler/code-generator.ts` around lines 1407 - 1424, The integer switch generation in the case-label processing within the surrounding code-generator method assumes every CaseLabel expression is a Literal; update it to also resolve enum ExpressionName constants to their ordinal values, matching the selector’s Enum.ordinal() conversion, before adding values to caseValues and caseLabelMap. Preserve literal handling for non-enum cases and default-label behavior.
🧹 Nitpick comments (5)
src/types/checker/environment.ts (1)
44-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider adding the
Enumbase members that the code generator depends on.The global
Enumentry is an emptyClassType. It declares noordinal(),name(), orcompareTo(...)members.src/compiler/code-generator.tsemitsINVOKEVIRTUAL java/lang/Enum.ordinal()Ifor enum switch selectors, so the runtime contract assumes those members exist. A source program that callsselector.ordinal()will fail type checking withCannotFindSymbolError, even though the compiler can emit the call.Adding at least
ordinal()returningintandname()returningStringkeeps the type environment consistent with the emitted bytecode.Using
ClassTyperather thanEnumClassfor the base is correct here, becausecheckSwitchExpressionmust not accept the abstract base as a selector.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types/checker/environment.ts` around lines 44 - 46, The global Enum entry in the type environment is missing members required by type checking and generated bytecode. Update the Enum ClassType declaration to add ordinal() returning int and name() returning String, preserving it as ClassType so checkSwitchExpression does not accept the abstract base as a selector.src/compiler/code-generator.ts (1)
1379-1398: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNarrow the
tryblock and correct themaxStackadjustment.Two points on this normalization block.
The
tryat line 1383 wraps both thequeryClasslookup and the bytecode emission.queryClassthrowsSymbolNotFoundErrorfor an unresolved name, which is the case this code intends to tolerate. The current form also swallows any failure fromindexMethodrefInfo. Wrap only the lookup, or check for the class before emitting.Line 1393 sets
maxStacktoexprStackSize + 1.ordinal()pops the objectref and pushes an int, so the net stack change is zero and the peak stays atexprStackSize. Over-reserving is safe for the verifier, but the extra slot is unnecessary and the expression suggests a growth that does not occur.♻️ Proposed refactor
let _resultType = resultType if (_resultType && _resultType.startsWith('L') && _resultType !== 'Ljava/lang/String;') { const clean = _resultType.replace(/^L|;$/g, '') + let classInfo try { - const classInfo = cg.symbolTable.queryClass(clean) - if (classInfo.accessFlags & ACCESS_FLAGS.ACC_ENUM) { - // call java.lang.Enum.ordinal() (returns int) - cg.code.push( - OPCODE.INVOKEVIRTUAL, - 0, - cg.constantPoolManager.indexMethodrefInfo('java/lang/Enum', 'ordinal', '()I') - ) - _resultType = 'I' - maxStack = Math.max(maxStack, exprStackSize + 1) - } - } catch (e) { - // ignore: not a known class + classInfo = cg.symbolTable.queryClass(clean) + } catch { + classInfo = undefined // not a known class + } + if (classInfo && classInfo.accessFlags & ACCESS_FLAGS.ACC_ENUM) { + // call java.lang.Enum.ordinal() (returns int) + cg.code.push( + OPCODE.INVOKEVIRTUAL, + 0, + cg.constantPoolManager.indexMethodrefInfo('java/lang/Enum', 'ordinal', '()I') + ) + _resultType = 'I' } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/compiler/code-generator.ts` around lines 1379 - 1398, Narrow the try/catch in the enum normalization block around cg.symbolTable.queryClass so only unresolved-class lookup failures are ignored; let indexMethodrefInfo and bytecode emission errors propagate. When emitting Enum.ordinal(), update maxStack using exprStackSize rather than exprStackSize + 1, since the invocation has zero net stack growth.src/types/checker/index.ts (2)
585-666: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftExtract the shared class-body checking logic instead of duplicating the
NormalClassDeclarationbranch.Lines 585-666 duplicate lines 487-583 almost verbatim. Only the declaration list source differs:
node.classBody.classBodyDeclarationsbecomesbodyDecls. The frame setup, the constructor index arithmetic, the field initializer check, the overload index computation, and the counter updates are identical.Two copies must now stay in sync. A fix applied to one branch will silently miss the other.
Extract a helper that takes
classType, the declaration list, and the frame, then call it from both branches.The
as anycasts at lines 622, 639, 642, and 656 are also avoidable. The surroundingswitch (bodyDeclaration.kind)already narrows the node type, and the equivalent class branch needs no casts. IfbodyDeclsis typed asany[], type the enum body declaration list properly so the narrowing works.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types/checker/index.ts` around lines 585 - 666, Extract the duplicated class-body checking flow from the NormalClassDeclaration and EnumDeclaration branches into a shared helper accepting classType, declaration list, and frame, preserving constructor indexing, field initializer checks, method overload resolution, and declaration counters. Invoke the helper from both branches, and type the enum declaration list so switch narrowing removes the unnecessary as any casts in the enum handling.
727-740: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the typed
caseConstantsfield for checker switch labels.
SwitchLabeldeclares onlycaseConstants, and the checker AST extractor emits that field. Remove singular-property probing andas anycasts. Narrow to thecaseConstantsvariant and iterate overswitchLabel.caseConstants. The separatesrc/ast/astExtractor/statement-extractor.tsmodel is not used by this checker.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types/checker/index.ts` around lines 727 - 740, Update the switch-label handling in the type-checker branch to use only the typed SwitchLabel.caseConstants field. Remove the singular caseConstant probing and all any casts, narrow to the caseConstants variant, and iterate directly over switchLabel.caseConstants while preserving the existing type-checking and error behavior.src/types/checker/prechecks.ts (1)
128-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueEnum constant and method registration look correct.
Registering each enum constant as a field whose type is the enum class matches Java semantics. The constructor and method handling mirrors the
NormalClassDeclarationpath.One consistency note: this branch returns on the first error, while the
NormalClassDeclarationpath accumulates errors and reports them together. Accumulating here would report all enum body problems in one pass.Also applies to: 173-185
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types/checker/prechecks.ts` around lines 128 - 159, The EnumDeclaration processing should accumulate errors from enum constants, constructors, and methods instead of returning on the first failure. Update the enum registration loops and createMethodLocal handling to collect TypeCheckerError instances, continue processing remaining declarations, and return the combined errors after the enum body has been processed, matching the NormalClassDeclaration path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/compiler/compiler-utils.ts`:
- Around line 9-10: Remove the unreachable enum entry from the class-modifier
mapping, or implement enum declaration parsing separately from
NormalClassDeclaration and assign ACCESS_FLAGS.ACC_ENUM from the declaration
kind rather than classModifier. Ensure ClassModifier remains limited to
supported class modifiers.
In `@src/types/checker/__tests__/switchStatements.test.ts`:
- Around line 76-100: Update the switch statement tests to use Java-valid
unqualified enum labels: change the positive case to case RED and retain the
incompatible case with an unqualified constant from Other, then add coverage for
resolving selector enum constants in case-label scope. Also add a test declaring
an enum at top level to exercise the registration path in prechecks alongside
the existing method-local declarations.
In `@src/types/checker/prechecks.ts`:
- Around line 223-234: The OrdinaryCompilationUnit branch of addClassParents
must traverse nested EnumDeclaration nodes, including enums declared inside
method bodies, before or alongside topLevelClassOrInterfaceDeclarations. Reuse
the existing nested-enum traversal pattern from addClasses or addClassMethods,
and ensure each discovered enum receives the Enum ClassType parent through the
existing parent-assignment logic.
- Around line 18-42: Extract the nested-enum traversal from registerNestedEnums
into one shared helper that descends into each top-level declaration’s children
without visiting the top-level declaration itself. Reuse this helper in the
declaration pass around registerNestedEnums and the pass containing
processNestedEnums, removing their local walkers and duplicate enum processing.
Also invoke the shared helper in the OrdinaryCompilationUnit branch of
addClassParents so nested enums receive the Enum parent. Apply these changes in
src/types/checker/prechecks.ts at lines 18-42, 91-107, and 223-234.
- Around line 160-172: Update the FieldDeclaration handling to convert
bodyNode.unannType with unannTypeToString before passing it to frame.getType,
while preserving the existing fieldType fallback; import unannTypeToString from
its defining module so enum field processing supplies the string expected by
getType.
In `@src/types/checker/statements.ts`:
- Around line 31-34: Update the selector validation around
isPrimitiveIntegralType, isPrimitiveLongType, isStringType, and EnumClass to
also accept boxed integral types Character, Byte, Short, and Integer, reusing
the existing type predicates or classes. Preserve rejection of Boolean and Long,
and add coverage for at least one boxed integral selector in
switchStatements.test.ts.
---
Outside diff comments:
In `@src/compiler/code-generator.ts`:
- Around line 1407-1424: The integer switch generation in the case-label
processing within the surrounding code-generator method assumes every CaseLabel
expression is a Literal; update it to also resolve enum ExpressionName constants
to their ordinal values, matching the selector’s Enum.ordinal() conversion,
before adding values to caseValues and caseLabelMap. Preserve literal handling
for non-enum cases and default-label behavior.
---
Nitpick comments:
In `@src/compiler/code-generator.ts`:
- Around line 1379-1398: Narrow the try/catch in the enum normalization block
around cg.symbolTable.queryClass so only unresolved-class lookup failures are
ignored; let indexMethodrefInfo and bytecode emission errors propagate. When
emitting Enum.ordinal(), update maxStack using exprStackSize rather than
exprStackSize + 1, since the invocation has zero net stack growth.
In `@src/types/checker/environment.ts`:
- Around line 44-46: The global Enum entry in the type environment is missing
members required by type checking and generated bytecode. Update the Enum
ClassType declaration to add ordinal() returning int and name() returning
String, preserving it as ClassType so checkSwitchExpression does not accept the
abstract base as a selector.
In `@src/types/checker/index.ts`:
- Around line 585-666: Extract the duplicated class-body checking flow from the
NormalClassDeclaration and EnumDeclaration branches into a shared helper
accepting classType, declaration list, and frame, preserving constructor
indexing, field initializer checks, method overload resolution, and declaration
counters. Invoke the helper from both branches, and type the enum declaration
list so switch narrowing removes the unnecessary as any casts in the enum
handling.
- Around line 727-740: Update the switch-label handling in the type-checker
branch to use only the typed SwitchLabel.caseConstants field. Remove the
singular caseConstant probing and all any casts, narrow to the caseConstants
variant, and iterate directly over switchLabel.caseConstants while preserving
the existing type-checking and error behavior.
In `@src/types/checker/prechecks.ts`:
- Around line 128-159: The EnumDeclaration processing should accumulate errors
from enum constants, constructors, and methods instead of returning on the first
failure. Update the enum registration loops and createMethodLocal handling to
collect TypeCheckerError instances, continue processing remaining declarations,
and return the combined errors after the enum body has been processed, matching
the NormalClassDeclaration path.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 476c8050-48ea-410f-8baa-c3a0e76dd316
📒 Files selected for processing (8)
src/compiler/code-generator.tssrc/compiler/compiler-utils.tssrc/types/checker/__tests__/switchStatements.test.tssrc/types/checker/environment.tssrc/types/checker/index.tssrc/types/checker/prechecks.tssrc/types/checker/statements.tssrc/types/types/classes.ts
- Updated grammar.pegjs and grammar.ts to add EnumDeclaration parsing - Added TopLevelClassOrInterfaceDeclaration and ClassMemberDeclaration alternatives for EnumDeclaration - Added EnumDeclaration, EnumBody, EnumConstantList, and EnumConstant parsing rules - Created src/compiler/__tests__/tests/enum.test.ts with 3 enum test cases - Updated src/compiler/__tests__/index.ts to import and run enum tests Remaining work: - Run enum compiler tests to verify parsing works - Implement enum code generation in compiler.ts (enum initialization, synthetic methods) - Run full test suite to validate no regressions - Verify enum runtime behavior (ordinal(), name(), values(), valueOf()) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Updated grammar (grammar.pegjs and grammar.ts) to parse enum declarations - EnumDeclaration, EnumBody, EnumConstantList, EnumConstant rules - Support for optional semicolon after constants and enum body members - Extended AST types (src/ast/types/classes.ts) - Added EnumDeclaration, EnumBody, EnumConstant interfaces - Updated ClassDeclaration union to include EnumDeclaration - Updated ClassBodyDeclaration to include EnumDeclaration - Added EnumDeclaration to NodeMap (src/ast/types/ast.ts) - Updated compiler to handle enum declarations - Added compileEnum() method in src/compiler/compiler.ts - Updated compile() to route EnumDeclaration through compileEnum() - Fixed type signatures to handle both ClassDeclaration and EnumDeclaration - Set enum parent to java/lang/Enum and ACC_ENUM flag - Updated ast-extractor.ts and ec-evaluator/utils.ts to accept ClassDeclaration[] - Updated searchMainMtdClass() to filter out enums - Created src/compiler/__tests__/tests/enum.test.ts with 3 test cases - enum switch and synthetic methods - enum values returns cloned array - enum constructors and instance fields Status: Enums parse and compile, but synthetic methods not yet implemented. Tests failing because ordinal(), name(), values(), valueOf() missing. Next: Implement synthetic enum method generation in compiler.ts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Added enumOrdinals Map to track enum constant ordinals - Registered ordinal(), name(), toString(), values(), valueOf() in symbol table - Fixed FieldInfo insertion to remove invalid 'ordinal' property - Fixed generateSimpleEnumMethod to use indexFieldrefInfo() Status: Compiler builds but enum compiler tests fail with: 1. Switch statement codegen doesn't recognize enum types 2. Bytecode generation may have structural issues Next: Fix enum type detection in switch codegen, then debug bytecode generation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* jvm changes * include try/catch/finally support in code generator * fix try statement logic * add parser and type checker integration * fix finally bug * add tests and fix syntax error * Patch grammar logic for throws keyword * Revert "Patch grammar logic for throws keyword" This reverts commit 8e933b6. * Patch grammar logic for throws keyword * Add fix for execption table finally logic * Add more tests * fix finally bug and missing test imports --------- Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>
* Include current and planned features in README * Update compiler README * Delete src/compiler/__tests__/tests/typeConversion.test.ts * Delete eslint.config.mjs * Add files via upload * Add files via upload --------- Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>
There was a problem hiding this comment.
🟡 Changes recommended
The current enum classfile generation path produces invalid/incorrect JVM semantics in key cases (superclass/flags/constructor alignment), which will likely break runtime execution and the newly added enum tests.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends switch-statement support by constraining the allowed selector types (String, integral primitives excluding long, and enums) and introduces enum parsing/type-checking/compilation paths to enable enum-based switching end-to-end.
Changes:
- Added an enum type representation (
EnumClass) and updated switch selector validation to permit String and enum selectors. - Implemented enum handling across the type-checker prechecks/body checks and expanded switch-case constant handling for differing AST shapes.
- Added compiler grammar + code generation support for enums (including enum switch lowering) and introduced compiler/type-checker tests for the new behavior.
File summaries
| File | Description |
|---|---|
| src/types/types/classes.ts | Introduces EnumClass in the type system. |
| src/types/checker/statements.ts | Narrows switch selector acceptance to String/integral/enum. |
| src/types/checker/prechecks.ts | Adds enum registration and enum member processing during prechecks. |
| src/types/checker/index.ts | Adds enum declaration body type-checking and expands switch label constant handling. |
| src/types/checker/environment.ts | Adds a global Enum type entry for enum parenting in the checker environment. |
| src/types/checker/tests/switchStatements.test.ts | Adds switch selector tests for String/Boolean rejection and enum selector/case typing. |
| src/ec-evaluator/utils.ts | Updates main-method class search to account for enum declarations. |
| src/compiler/symbol-table.ts | Extends symbol metadata to track enums and enum constant ordinals. |
| src/compiler/grammar.ts | Adds enum declarations and restricts switch labels to literal/identifier forms. |
| src/compiler/grammar.pegjs | Mirrors grammar changes for enum declarations and switch label parsing. |
| src/compiler/compiler.ts | Adds enum compilation path, enum synthetic members, and compilation ordering adjustments. |
| src/compiler/compiler-utils.ts | Adds enum access flag mapping for class access flags generation. |
| src/compiler/code-generator.ts | Lowers enum switches by converting selector to ordinal and mapping case labels to ordinals. |
| src/compiler/tests/tests/enum.test.ts | Adds end-to-end JVM execution tests for enum features and enum switches. |
| src/compiler/tests/index.ts | Registers the new enum test suite. |
| src/ast/types/classes.ts | Extends compiler AST types to represent enums. |
| src/ast/types/ast.ts | Adds EnumDeclaration to the compiler AST node map. |
| src/ast/astExtractor/ast-extractor.ts | Broadens top-level declaration extraction typing to include enums. |
Review details
Suppressed comments (2)
src/types/checker/prechecks.ts:71
Frame.setType()returnsnull | TypeCheckerError(notError), so this duplicate-type check will never trigger for enums; the method will incorrectly succeed even when the enum type name is already defined.
if (error instanceof Error) return newResult(null, [new DuplicateClassError(node.location)])
src/compiler/compiler.ts:208
- When
bodyMembers.length !== 0you skip generating the synthetic enum constructor/ordinal method, but<clinit>still unconditionally invokes<init>(Ljava/lang/String;I)Vfor each constant (seeaddEnumStaticInitializer). This will produce invalid bytecode for enums with explicit constructors/fields, and enum constant argument lists are currently ignored (e.g.EARTH(1)).
if (bodyMembers.length === 0) {
this.addEnumConstructor()
this.addEnumOrdinalMethod()
} else {
this.handleClassBody(bodyMembers)
}
- Files reviewed: 18/18 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| })() | ||
| : parseInt((label.expression as Literal).literalType.value) |
| this.className = enumNode.typeIdentifier | ||
| this.parentClassName = 'java/lang/Object' | ||
| const accessFlags = generateClassAccessFlags(enumNode.classModifier) |
| try { | ||
| const enumType = new EnumClass(obj.typeIdentifier.identifier) | ||
| const err = frame.setType(obj.typeIdentifier.identifier, enumType, obj.typeIdentifier.location) | ||
| if (err instanceof Error) { |
| // ldc EnumClass.class | ||
| bytecode.push(0x12) // ldc | ||
| const classRefIndex = this.constantPoolManager.indexClassInfo(this.className) | ||
| bytecode.push(classRefIndex & 0xff) | ||
|
|
This PR aims to build on existing support for switch statements by completing the type of selector accepted, in particular narrowing it down to String, integral types or enum types.
Note: For the purpose of creating unit tests, enum support has been created in the compiler. The support in the JVM will be added along with the standard libraries update.